home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / BBS_UTL / BBSKIT31 / MTASK20.ZIP / MTASK.PAS < prev    next >
Pascal/Delphi Source File  |  1990-03-15  |  13KB  |  515 lines

  1. UNIT mtask;
  2.  
  3.  
  4. {MTASK 2.0, a simple multi-tasker unit for Turbo Pascal 5.
  5.  
  6. Written in November, 1988, and donated to the public domain by:
  7.  
  8.    Wayne E. Conrad
  9.    2627 North 51st Ave, #219
  10.    Phoenix, AZ  85035
  11.    BBS: (602) 484-9356, 300/1200/2400, 24 hours/day
  12.  
  13. This unit provides Turbo Pascal 5 with what I call "request driven"
  14. multi-tasking.  Switching from the current task to another task is done
  15. whenever the current task requests a task switch by calling procedure
  16. "switch_task."  No interrupt driven context switching is done, because
  17. it's a hassle.
  18.  
  19. See accompanying files for documentation and examples.}
  20.  
  21.  
  22. {$F+}  {Most procedures in this unit must be FAR}
  23.  
  24.  
  25. INTERFACE
  26.  
  27.  
  28. {Result codes.  0 is "no error"}
  29.  
  30. CONST
  31.   heap_full       = 1;   {Unable to allocate heap for the task's stack}
  32.   too_many_tasks  = 2;   {Maximum number of tasks are already running}
  33.   invalid_task_id = 3;   {There is no task with that ID number}
  34.  
  35.  
  36. {This is the procedure type for a task.  The parent task can pass any
  37. type of variable to  the child task.}
  38.  
  39. TYPE
  40.   task_proc = PROCEDURE (VAR param);
  41.  
  42.  
  43. {See the IMPLEMENTATION section for descriptions of these procedures and
  44. functions.}
  45.  
  46. PROCEDURE create_task
  47.   (
  48.   task      : task_proc;
  49.   VAR param ;
  50.   stack_size: Word;
  51.   VAR id    : Word;
  52.   VAR result: Word
  53.   );
  54. PROCEDURE terminate_task (id: Word; VAR result: Word);
  55. PROCEDURE switch_task;
  56. FUNCTION current_task_id: Word;
  57. FUNCTION number_of_tasks: Word;
  58.  
  59.  
  60. IMPLEMENTATION
  61.  
  62.  
  63. {The maximum number of tasks.  Modify to suit your needs.}
  64.  
  65. CONST
  66.   max_tasks = 16;
  67.  
  68.  
  69. {This record contains all the information about a task, as follows:
  70.  
  71.   stack_ptr:   Saved stack segment (ss) and stack pointer (sp) registers
  72.  
  73.   stack_org:   If the stack is stored on the heap, this is the address of
  74.                the beginning of the block of memory allocated for the stack.
  75.  
  76.   stack_bytes: Size of stack on the heap, or 0 if the stack is not on the
  77.                heap.  If the stack is not on the heap, then this field is 0.
  78.  
  79.   bp:          Saved value of base pointer (BP) register.
  80.  
  81.   id:          The id number of the task
  82.  
  83. Note that DS (Data Segment register) is not stored.  We can get away with
  84. this by assuming that all tasks will use the same data segment.}
  85.  
  86. TYPE
  87.   task_rec =
  88.     RECORD
  89.     stack_ptr  : Pointer;
  90.     stack_org  : Pointer;
  91.     stack_bytes: Word;
  92.     bp         : Word;
  93.     id         : Word;
  94.     END;
  95.  
  96.  
  97. {The number of tasks in the system}
  98.  
  99. VAR
  100.   ntasks: Word;
  101.  
  102.  
  103. {Information for each task.}
  104.  
  105. VAR
  106.   task_info: ARRAY [1..max_tasks] OF task_rec;
  107.  
  108.  
  109. {The last task ID assigned.  If we haven't rolled the id's over, then
  110. this allows us to assign task ID's without checking to see what id's have
  111. been assigned.}
  112.  
  113. VAR
  114.   last_id    : Word;
  115.   id_rollover: Boolean;
  116.  
  117.  
  118. {This is the task number of the currently executing task}
  119.  
  120. VAR
  121.   current_task: Word;
  122.  
  123.  
  124. {This is the record type of the initial contents of the stack when a task
  125. is created.  When the task is first switched to, it will be from within
  126. the switch_task, terminate_task, or terminate_current_task procedure.  At
  127. the end of switch_task, BP will be popped, then a far return will be
  128. done.  The far return will transfer to the beginning of task.  The task
  129. can access the parameter "task_param," which is a pointer to whatever
  130. data structure that the creator of this task wanted to pass to the new
  131. task.  When the task finally exits, a far return to "end_task" will be
  132. done.  The exception is the main task, which ends the program completely
  133. if it exits.}
  134.  
  135. TYPE
  136.   initial_stack_rec_ptr = ^initial_stack_rec;
  137.   initial_stack_rec =
  138.     RECORD
  139.     bp        : Word;
  140.     task_addr : task_proc;
  141.     end_task  : Pointer;
  142.     task_param: Pointer;
  143.     END;
  144.  
  145.  
  146. {Given a task ID, return the task number, or 0 if there is no task with
  147. that ID.}
  148.  
  149. FUNCTION find_task (target_id: Word): Word;
  150. VAR
  151.   n: Word;
  152. BEGIN
  153.   n := 1;
  154.   WHILE (n <= ntasks) AND (task_info [n].id <> target_id) DO
  155.     Inc (n);
  156.   IF (n > ntasks) THEN
  157.     n := 0;
  158.   find_task := n
  159. END;
  160.  
  161.  
  162. {Remove a task's information from the task info array, and decrement the
  163. number of tasks.}
  164.  
  165. PROCEDURE delete_task_info (task_num: Word);
  166. VAR
  167.   i: Word;
  168. BEGIN
  169.   FOR i := task_num TO ntasks - 1 DO
  170.     task_info [i] := task_info [i + 1];
  171.   Dec (ntasks)
  172. END;
  173.  
  174.  
  175. {Terminate the current task.  If the current task is the only task, then
  176. the program is halted.  If the current task's stack was allocated from
  177. the heap, it is freed.}
  178.  
  179. PROCEDURE terminate_current_task;
  180.  
  181.  
  182. {These are defined as constants to force them into the data segment.
  183. They can't be local, because local variables are stored on the stack and
  184. we're going to switch to a different task (and therefore to a different
  185. stack) before we're done with these variables.}
  186.  
  187. CONST
  188.   old_stack_org  : Pointer = NIL;
  189.   old_stack_bytes: Word = 0;
  190.  
  191.  
  192. VAR
  193.   task_num : Word;
  194.   new_stack: Pointer;
  195.   new_bp   : Word;
  196.  
  197.  
  198. BEGIN {terminate_current_task}
  199.  
  200.   {If we're the last task left, then exit to DOS}
  201.  
  202.   IF ntasks <= 1 THEN
  203.     Halt;
  204.  
  205.   {Remember where the task's stack is so that we can free it up if it's
  206.   on the heap.  We can't free it now, because we're still using it!}
  207.  
  208.   WITH task_info [current_task] DO
  209.     BEGIN
  210.     old_stack_org   := stack_org;
  211.     old_stack_bytes := stack_bytes
  212.     END;
  213.  
  214.   {Remove the task's information from the task info array}
  215.  
  216.   delete_task_info (current_task);
  217.   IF current_task > ntasks THEN
  218.     current_task := 1;
  219.  
  220.   {Switch to the next task.  The stack_ptr and bp are transfered into
  221.   local variables because it's much easier to access simple variables in
  222.   INLINE code than it is to access array variables.}
  223.  
  224.   WITH task_info [current_task] DO
  225.     BEGIN
  226.     new_stack := stack_ptr;
  227.     new_bp    := bp
  228.     END;
  229.   INLINE
  230.     (
  231.     $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  232.     $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  233.     $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  234.     $fa/                      {CLI}
  235.     $8e/$d2/                  {MOV  SS,DX}
  236.     $8b/$e0/                  {MOV  SP,AX}
  237.     $fb                       {STI}
  238.     );
  239.  
  240.   {If the task we just got rid of had its heap on the stack, then release
  241.   that memory back to the free pool.}
  242.  
  243.   IF old_stack_bytes > 0 THEN
  244.     FreeMem (old_stack_org, old_stack_bytes)
  245.  
  246. END;
  247.  
  248.  
  249. {Terminate a task.  If task_id is 0, then the current task is deleted.
  250. Possible result codes are:
  251.  
  252.   0                   No error
  253.   invalid_task_id     There is no task with that ID number}
  254.  
  255. PROCEDURE terminate_task (id: Word; VAR result: Word);
  256.  
  257.  
  258.   {Delete a task.  Do not use to delete the current task!}
  259.  
  260.   PROCEDURE delete_task (task_num: Word);
  261.   BEGIN
  262.     WITH task_info [task_num] DO
  263.       IF stack_bytes > 0 THEN
  264.         FreeMem (stack_org, stack_bytes);
  265.     delete_task_info (task_num);
  266.     IF current_task > task_num THEN
  267.       Dec (current_task)
  268.   END;
  269.  
  270.  
  271. VAR
  272.   task_num: Word;
  273.  
  274. BEGIN {terminate_task}
  275.   result := 0;
  276.   IF id = 0 THEN
  277.     terminate_current_task
  278.   ELSE
  279.     BEGIN
  280.     task_num := find_task (id);
  281.     IF task_num = 0 THEN
  282.       result := invalid_task_id
  283.     ELSE
  284.       IF task_num = current_task THEN
  285.         terminate_current_task
  286.       ELSE
  287.         delete_task (task_num)
  288.     END
  289. END;
  290.  
  291.  
  292. {Create a new task and pass parameter "param" to it.  Stack space for the
  293. task is allocated from the heap, and the stack is initialized so that
  294. procedure "new_task" will be executed with parameter "param".  Result
  295. codes are:
  296.  
  297.   0                  No error occured
  298.   heap_full          Unable to allocate heap for the task's stack
  299.   too_many_tasks     Maximum number of tasks are already running
  300.  
  301. If an error occurs, then id is not set.  Otherwise, id is the task id of
  302. the newly created task.}
  303.  
  304. PROCEDURE create_task
  305.   (
  306.   task      : task_proc;
  307.   VAR param ;
  308.   stack_size: Word;
  309.   VAR id    : Word;
  310.   VAR result: Word
  311.   );
  312.  
  313.  
  314. {This is the task number of the task we're creating}
  315.  
  316. VAR
  317.   task_num: Word;
  318.  
  319.  
  320.   {Allocate stack space for the task.  The minimum allowable requested
  321.   stack size is 512 bytes.  For some reason, the stack-check procedure in
  322.   Turbo's run-time library has that limit hard-coded into it.
  323.  
  324.   stack_org is set to the address of the beginning of the block of memory
  325.   allocated for the stack.
  326.  
  327.   stack_bytes is set to the size of the block of memory allocated for the
  328.   stack.}
  329.  
  330.   PROCEDURE create_stack;
  331.   BEGIN
  332.     IF stack_size < 512 THEN
  333.       stack_size := 512;
  334.     IF stack_size > MaxAvail THEN
  335.       result := heap_full
  336.     ELSE
  337.       WITH task_info [task_num] DO
  338.         BEGIN
  339.         GetMem (stack_org, stack_size);
  340.         stack_bytes := stack_size
  341.         END
  342.   END;
  343.  
  344.  
  345.   {Initialize the stack and the stack pointer.  The structure
  346.   "initial_stack_rec" is placed at the top of the stack area, with the
  347.   stack pointer pointing to its lowest element.  See the comments for
  348.   initial_stack_rec for what the stuff in initial_stack_rec actually
  349.   does.}
  350.  
  351.   PROCEDURE init_stack;
  352.   VAR
  353.     stack_ofs: Word;
  354.   BEGIN
  355.     WITH task_info [task_num] DO
  356.       BEGIN
  357.       stack_ofs := Ofs (stack_org^) + stack_bytes - Sizeof (initial_stack_rec);
  358.       stack_ptr := Ptr (Seg (stack_org^), stack_ofs);
  359.       bp := Ofs (stack_ptr^);
  360.       WITH initial_stack_rec_ptr (stack_ptr)^ DO
  361.         BEGIN
  362.         task_param := @param;
  363.         task_addr  := task;
  364.         end_task   := @terminate_current_task;
  365.         bp         := 0
  366.         END
  367.       END
  368.   END;
  369.  
  370.  
  371.   {Find an unused task id and assign it to the new task}
  372.  
  373.   PROCEDURE assign_task_id;
  374.  
  375.  
  376.     {Increment "last_id" to (hopefully) turn it into the task_id we're
  377.     going to assign.  If it rolls over, set it to 2 (task 1 will always
  378.     exist, since it's the root task) and remember that we've rolled
  379.     over.}
  380.  
  381.     PROCEDURE increment_last_id;
  382.     BEGIN
  383.       IF last_id = 65535 THEN
  384.         BEGIN
  385.         last_id := 2;
  386.         id_rollover := True
  387.         END
  388.       ELSE
  389.         Inc (last_id)
  390.     END;
  391.  
  392.  
  393.   BEGIN {assign_task_id}
  394.     increment_last_id;
  395.     IF id_rollover THEN
  396.       WHILE (find_task (last_id) <> 0) DO
  397.         increment_last_id;
  398.     id := last_id;
  399.     task_info [task_num].id := id
  400.   END;
  401.  
  402.  
  403. BEGIN {create_task}
  404.   result := 0;
  405.   IF ntasks >= max_tasks THEN
  406.     result := too_many_tasks
  407.   ELSE
  408.     BEGIN
  409.     task_num := Succ (ntasks);
  410.     create_stack;
  411.     IF result = 0 THEN
  412.       BEGIN
  413.       init_stack;
  414.       assign_task_id;
  415.       Inc (ntasks)
  416.       END
  417.     END
  418. END;
  419.  
  420.  
  421. {Switch to the next task}
  422.  
  423. PROCEDURE switch_task;
  424.  
  425. VAR
  426.   new_stack: Pointer;
  427.   old_bp   : Word;
  428.   new_bp   : Word;
  429.  
  430. BEGIN
  431.  
  432.   {Only switch if there are other tasks to switch to}
  433.  
  434.   IF ntasks > 1 THEN
  435.     BEGIN
  436.  
  437.     {Save the current value of SS, SP, and BP for this task}
  438.  
  439.     INLINE
  440.       (
  441.       $89/$ae/>old_bp           {MOV  OLD_BP,BP}
  442.       );
  443.     WITH task_info [current_task] DO
  444.       BEGIN
  445.       stack_ptr := Ptr (Sseg, Sptr);
  446.       bp        := old_bp
  447.       END;
  448.  
  449.     {Switch to the next task.  The bit with new_stack and new_bp are
  450.     because it's easier to write INLINE code to access a simple variable
  451.     than it is to access a record of an array.}
  452.  
  453.     IF current_task >= ntasks THEN
  454.       current_task := 1
  455.     ELSE
  456.       Inc (current_task);
  457.     WITH task_info [current_task] DO
  458.       BEGIN
  459.       new_stack := stack_ptr;
  460.       new_bp    := bp
  461.       END;
  462.     INLINE
  463.       (
  464.       $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  465.       $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  466.       $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  467.       $Fa/                      {CLI}
  468.       $8e/$d2/                  {MOV  SS,DX}
  469.       $8b/$e0/                  {MOV  SP,AX}
  470.       $fb                       {STI}
  471.       )
  472.     END
  473. END;
  474.  
  475.  
  476. {Return the id number of the currently executing task}
  477.  
  478. FUNCTION current_task_id: Word;
  479. BEGIN
  480.   current_task_id := task_info [current_task].id
  481. END;
  482.  
  483.  
  484. {Return the number of tasks}
  485.  
  486. FUNCTION number_of_tasks: Word;
  487. BEGIN
  488.   number_of_tasks := ntasks
  489. END;
  490.  
  491.  
  492. {Initialize this unit.  The task list is initialized to contain the
  493. current task, whose task id is 1.}
  494.  
  495. PROCEDURE init_mtask;
  496. VAR
  497.   id: Word;
  498. BEGIN
  499.   ntasks := 1;
  500.   current_task := 1;
  501.   WITH task_info [current_task] DO
  502.     BEGIN
  503.     stack_org   := NIL;
  504.     stack_bytes := 0;
  505.     id          := 1
  506.     END;
  507.   last_id := 1;
  508.   id_rollover := False
  509. END;
  510.  
  511.  
  512. BEGIN {mtask}
  513.   init_mtask
  514. END.
  515.